﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Xml;
using System.Xml.Linq;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;

using Hetymine.Executioner.Plugins;
using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Controls;

/* TODO: Sanitize config values so as not to cause errors.
 * -Set propper warning/error colours of textboxes on load
 * -Remember last config page
 */
namespace Hetymine.Executioner {
    /// <summary>
    /// Interaction logic for Config.xaml
    /// </summary>
    public partial class Config : Window {
        private Executioner _Exec;
        private IniFile _Config;
        private List<HotKey> _HotKeys;
        private Dictionary<string, List<ICommand>> _Commands;
        private Dictionary<string, int> _SelectedCommandIndexes;
        private string _CommandType = string.Empty;
        private ICommand _SelectedCommand;
        private CommandNameEventHandler _CommandChanged;
        private bool _IsApply = true;

        private System.Windows.Forms.FontDialog _FontDialog = new System.Windows.Forms.FontDialog();
        
        public Config(Executioner executioner) {
            InitializeComponent();

            this._Exec = executioner;

            // Clone existing config values incase of reset
            this._Config = Executioner.Config.Clone() as IniFile;
            this._HotKeys = this.HotKey_Clone();
            this._Commands = this.Commands_Clone();

            // Add plugin config panels
            var plugins = from p in this._Exec.PluginService.Plugins.Keys
                          where this._Exec.PluginService.Plugins[p].Instance is IConfigurable<IniFile>
                          select p;
            foreach (string p in plugins) {
                IConfigurable<IniFile> c = this._Exec.PluginService.Plugins[p].Instance as IConfigurable<IniFile>;
                Grid g = new Grid();
                g.Name = "pnlPlugins" + p;
                g.Visibility = Visibility.Hidden;
                g.Children.Add(c.Configurator.ConfigPanel);
                this.pnlCollection.Children.Add(g);
                triPlugins.Visibility = Visibility.Visible;
                TreeViewItem tvi = new TreeViewItem();
                tvi.Header = p;
                triPlugins.Items.Add(tvi);
            }

            // Ensure hotstring command dictionary exists
            if (!this._Commands.ContainsKey("HotString")) {
                this._Commands["HotString"] = new List<ICommand>();
            }
            
            // Add plugin command tree items
            plugins = from p in this._Exec.PluginService.Plugins.Keys
                          where this._Exec.PluginService.Plugins[p].Instance is CommandPlugin
                          select p;
            foreach (string p in plugins) {
                // In case there are no commands for the item when cloned,
                // ensure that an empty dictionary exists for it
                string type = p.Replace(" ", string.Empty);
                if (!this._Commands.ContainsKey(type)) {
                    this._Commands[type] = new List<ICommand>();
                }
                TreeViewItem tvi = new TreeViewItem();
                tvi.Header = p;
                triCommands.Items.Add(tvi);
            }

            this.trvNavigation.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(Navigation_SelectedItemChanged);

            this.btnResetAll.Click += new RoutedEventHandler(Button_ResetAll);
            this.btnResetPage.Click += new RoutedEventHandler(Button_ResetPage);
            this.btnClose.Click += new RoutedEventHandler(Button_Close);
            this.btnFont.Click += new RoutedEventHandler(Button_Font);

            // TODO: Make this feature work lols
            this.btnResetPage.Visibility = Visibility.Collapsed;

            // Appearance update events
            this.cfgOuterBorderColour.TextChanged += new TextChangedEventHandler(Colour_TextChanged);
            this.cfgInnerBorderColour.TextChanged += new TextChangedEventHandler(Colour_TextChanged);
            this.cfgBackgroundColour.TextChanged += new TextChangedEventHandler(Colour_TextChanged);
            this.cfgForegroundColour.TextChanged += new TextChangedEventHandler(Colour_TextChanged);
            this.cfgOuterBorderWidth.TextChanged += new TextChangedEventHandler(Width_TextChanged);
            this.cfgInnerBorderWidth.TextChanged += new TextChangedEventHandler(Width_TextChanged);
            this.cfgPadding.TextChanged += new TextChangedEventHandler(Width_TextChanged);
            this.cfgOuterBorderRadius.TextChanged += new TextChangedEventHandler(Width_TextChanged);
            this.cfgInnerBorderRadius.TextChanged += new TextChangedEventHandler(Width_TextChanged);
            this.cfgFontFamily.TextChanged += new TextChangedEventHandler(FontFamily_TextChanged);
            this.cfgFontSize.TextChanged += new TextChangedEventHandler(FontSize_TextChanged);
            this.cfgBold.Click += new RoutedEventHandler(FontStyle_Click);
            this.cfgItalic.Click += new RoutedEventHandler(FontStyle_Click);
            this.cfgMargin.TextChanged += new TextChangedEventHandler(Margin_TextChanged);
            this.cfgAlignment.SelectionChanged += new SelectionChangedEventHandler(Alignment_SelectionChanged);

            // Behaviour update events
            this.cfgCommandBoxHotKey.TextChanged += new TextChangedEventHandler(Hotkey_TextChanged);
            this.cfgDefaultCommand.TextChanged += new TextChangedEventHandler(DefaultCommand_TextChanged);
            this.cfgUrlEndings.TextChanged += new TextChangedEventHandler(List_TextChanged);
            this.cfgUrlProtocals.TextChanged += new TextChangedEventHandler(List_TextChanged);
            this.cfgEnableSpeech.Click += new RoutedEventHandler(Check_Click);
            this.cfgSmartUrlParsing.Click += new RoutedEventHandler(Check_Click);
            this.cfgDisplay.SelectionChanged += new SelectionChangedEventHandler(Display_SelectionChanged);

            // Hot key update events
            this.hkHotKey.TextChanged += new TextChangedEventHandler(HotKey_HotKey_TextChanged);            
            this.hkCommand.CommandChanged += new EventHandler(HotKey_Command_CommandChanged);
            this.hkAssignedList.SelectionChanged += new SelectionChangedEventHandler(HotKey_AssignedList_SelectionChanged);
            this.hkAddHotKey.Click += new RoutedEventHandler(HotKey_Button_AddHotKey);
            this.hkRemoveHotKey.Click += new RoutedEventHandler(HotKey_Button_RemoveHotKey);
            this.hkAddCommand.Click += new RoutedEventHandler(HotKey_Button_AddCommand);
            this.hkRemoveCommand.Click += new RoutedEventHandler(HotKey_Button_RemoveCommand);

            // Command update events
            this.cmdAssignedList.SelectionChanged += new SelectionChangedEventHandler(Command_AssignedList_SelectionChanged);
            this.cmdAddCommand.Click += new RoutedEventHandler(Command_AddCommand_Click);
            this.cmdRemoveCommand.Click += new RoutedEventHandler(Command_RemoveCommand_Click);
            this._CommandChanged = new CommandNameEventHandler(Configurator_CommandNameChanged);
         
            this.Closing += new System.ComponentModel.CancelEventHandler(Config_Closing);            

            this.InitializeConfig();
        }

        #region Field Initialization
        private void InitializeConfig() {
            this.InitializeAppearance();
            this.UpdatePreview();

            this.InitializeBehaviour();

            this.InitializeHotKeys();

            this.InitializeCommands();

            this.InitializePlugins();
        }
        private void InitializeAppearance() {
            this._IsApply = false;

            // Outer border
            this.cfgOuterBorderColour.Text = this._Config.GetString("Appearance", "Outer Border Colour", "#313131").ToUpper();
            this.cfgOuterBorderWidth.Text = this._Config.GetInt("Appearance", "Outer Border Width", 2).ToString();
            this.cfgOuterBorderRadius.Text = this._Config.GetInt("Appearance", "Outer Border Radius", 6).ToString();

            // Inner border
            this.cfgInnerBorderColour.Text = this._Config.GetString("Appearance", "Inner Border Colour", "#FBC23E").ToUpper();
            this.cfgInnerBorderWidth.Text = this._Config.GetInt("Appearance", "Inner Border Width", 2).ToString();
            this.cfgInnerBorderRadius.Text = this._Config.GetInt("Appearance", "Inner Border Radius", 4).ToString();

            // Command box
            this._FontDialog.ShowColor = false;
            this._FontDialog.ShowApply = false;
            this._FontDialog.ShowEffects = false;
            this._FontDialog.ShowHelp = false;
            this._FontDialog.FontMustExist = true;

            System.Drawing.Font f = Styles.Font(this._Config);
            this.cfgBold.IsChecked = f.Bold;
            this.cfgItalic.IsChecked = f.Italic;
            this.cfgFontFamily.Text = f.FontFamily.Name;
            this.cfgFontSize.Text = ((int)Math.Round(f.Size, MidpointRounding.AwayFromZero)).ToString();
            this._FontDialog.Font = f;

            this.cfgBackgroundColour.Text = this._Config.GetString("Appearance", "Background Colour", "#313131").ToUpper();
            this.cfgForegroundColour.Text = this._Config.GetString("Appearance", "Foreground Colour", "#FBC23E").ToUpper();

            // Alignment and padding
            this.cfgMargin.Text = this._Config.GetInt("Appearance", "Margin", 10).ToString();
            this.cfgPadding.Text = this._Config.GetInt("Appearance", "Padding", 3).ToString();
            this.cfgAlignment.SelectedIndex = this.AppearanceAlignmentIndex();

            this._IsApply = true;
        }
        private void InitializeBehaviour() {
            this._IsApply = false;

            // Command Options
            this.cfgCommandBoxHotKey.Text = this._Config.GetString("Behaviour", "Command Box Hot Key", "Alt+C");
            this.cfgDefaultCommand.Text =  this._Config.GetString("Behaviour", "Default Command", string.Empty);
            this.cfgEnableSpeech.IsChecked = this._Config.GetBool("Behaviour", "Enable Speech", false);

            // Url Handling
            this.cfgUrlEndings.Text = this._Config.GetString("Behaviour", "Url Endings", "com, net, org, ca, html, htm, php, asp, aspx");
            this.cfgUrlProtocals.Text = this._Config.GetString("Behaviour", "Url Protocals", "http, https, ftp, ftps, irc");
            this.cfgSmartUrlParsing.IsChecked = this._Config.GetBool("Behaviour", "Smart Url Parsing", true);

            // Multiple Displays
            ComboBoxItem cbi = new ComboBoxItem();
            System.Windows.Forms.Screen prim = System.Windows.Forms.Screen.PrimaryScreen;
            cbi.Content = "Primary Display" + " (" + prim.Bounds.Width.ToString() + "×" + prim.Bounds.Height.ToString() + ")";
            this.cfgDisplay.Items.Add(cbi);            
            int display = 0;
            foreach (System.Windows.Forms.Screen s in System.Windows.Forms.Screen.AllScreens) {
                ++display;
                cbi = new ComboBoxItem();
                cbi.Content = "Display " + display.ToString() + " (" + s.Bounds.Width.ToString() + "×" + s.Bounds.Height.ToString() + ")";
                this.cfgDisplay.Items.Add(cbi);
            }
            
            this.cfgDisplay.SelectedIndex = DisplayIndex();

            if (System.Windows.Forms.Screen.AllScreens.Length <= 1) {
                this.grpDisplay.Visibility = Visibility.Collapsed;
            }

            this._IsApply = true;
        }
        private void InitializeHotKeys() {
            this.HotKey_UpdateAssignedList();

            if (this.hkAssignedList.Items.Count > 0) {
                this.hkAssignedList.SelectedIndex = 0;
                // Enable incase of reset
                this.hkRemoveHotKey.IsEnabled = true;
            }
            else {
                this.hkRemoveHotKey.IsEnabled = false;
            }
        }
        private void InitializeCommands() {
            this._SelectedCommandIndexes = new Dictionary<string, int>();

            // Global command list
            this._SelectedCommandIndexes.Add(string.Empty, -1);

            foreach (string s in this._Commands.Keys) {
                this._SelectedCommandIndexes.Add(s, -1);
            }
        }
        private void InitializePlugins() {
            var plugins = from p in this._Exec.PluginService.Plugins.Values
                          where p.Instance is IConfigurable<IniFile>
                          select p.Instance;

            foreach (IConfigurable<IniFile> p in plugins) {
                p.Configurator.InitializeConfig(this._Config);
            }
        }
        #endregion

        #region Appearance
        private void Colour_TextChanged(object sender, RoutedEventArgs e) {
            if (!this._IsApply) { return; }

            TextBox t = sender as TextBox;

            if (!Regex.IsMatch(t.Text, "^#([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")) {
                t.Background = Brushes.LightPink;
            }
            else {
                t.Background = SystemColors.ControlLightLightBrush;
                
                switch (t.Name) {
                    case "cfgOuterBorderColour":
                        this._Config.SetString("Appearance", "Outer Border Colour", t.Text.ToUpper());
                        break;
                    case "cfgInnerBorderColour":
                        this._Config.SetString("Appearance", "Inner Border Colour", t.Text.ToUpper());
                        break;
                    case "cfgBackgroundColour":
                        this._Config.SetString("Appearance", "Background Colour", t.Text.ToUpper());
                        break;
                    case "cfgForegroundColour":
                        this._Config.SetString("Appearance", "Foreground Colour", t.Text.ToUpper());
                        break;
                }

                this.UpdatePreview();
            }
        }
        private void Width_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            TextBox t = sender as TextBox;

            bool valid = true;

            int value;
            if (!int.TryParse(t.Text, out value)) {
                valid = false;
            }
            else if (value < 0) {
                valid = false;
            }
            
            if (!valid) {
                t.Background = Brushes.LightPink;
            }            
            else {
                if (value > 10) {
                    t.Background = Brushes.LightYellow;
                }
                else {
                    t.Background = SystemColors.ControlLightLightBrush;
                }

                switch (t.Name) {
                    case "cfgOuterBorderWidth":
                        this._Config.SetInt("Appearance", "Outer Border Width", value);
                        break;
                    case "cfgInnerBorderWidth":
                        this._Config.SetInt("Appearance", "Inner Border Width", value);
                        break;
                    case "cfgOuterBorderRadius":
                        this._Config.SetInt("Appearance", "Outer Border Radius", value);
                        break;
                    case "cfgInnerBorderRadius":
                        this._Config.SetInt("Appearance", "Inner Border Radius", value);
                        break;
                    case "cfgPadding":
                        this._Config.SetInt("Appearance", "Padding", value);
                        break;
                }

                this.UpdatePreview();
            }
        }
        private void FontFamily_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            TextBox t = sender as TextBox;
            System.Drawing.FontFamily fm = new System.Drawing.FontFamily(t.Text.Trim());

            if (fm.Name != this.cfgFontFamily.Text.Trim()) {
                t.Background = Brushes.LightPink;
            }
            else {
                t.Background = SystemColors.ControlLightLightBrush;
                this._Config.SetString("Appearance", "Font Family", fm.Name);
                this.UpdatePreview();
            }
        }
        private void FontSize_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            TextBox t = sender as TextBox;

            bool valid = true;

            int value;
            if (!int.TryParse(t.Text, out value)) {
                valid = false;
            }
            else if (value < 6) {
                valid = false;
            }

            if (!valid) {
                t.Background = Brushes.LightPink;
            }
            else {
                if (value > 72) {
                    t.Background = Brushes.LightYellow;
                }
                else {
                    t.Background = SystemColors.ControlLightLightBrush;
                }

                this._Config.SetInt("Appearance", "Font Size", value);

                this.UpdatePreview();
            }
        }
        private void Margin_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            TextBox t = sender as TextBox;

            bool valid = true;

            int value;
            if (!int.TryParse(t.Text, out value)) {
                valid = false;
            }
            else if (value < 0) {
                valid = false;
            }

            if (!valid) {
                t.Background = Brushes.LightPink;
            }
            else {
                System.Drawing.Rectangle r = Styles.Display().WorkingArea;
                int max = (int)((Math.Min(r.Width, r.Height) - 300) / 2);
                if (value > max) {
                    t.Background = Brushes.LightYellow;
                }
                else {
                    t.Background = SystemColors.ControlLightLightBrush;
                }

                this._Config.SetInt("Appearance", "Margin", value);
            }
        }
        private void Alignment_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if (!this._IsApply) { return; }

            ComboBoxItem cb = e.AddedItems[0] as ComboBoxItem;
            string alignment = cb.Content.ToString().ToLower();
            this._Config.SetString("Appearance", "Alignment", alignment);
        }
        private void FontStyle_Click(object sender, RoutedEventArgs e) {
            if (!this._IsApply) { return; }

            string style;

            if (this.cfgBold.IsChecked.Value) {
                style = "bold";

                if (this.cfgItalic.IsChecked.Value) {
                    style += ", italic";
                }
            }
            else if (this.cfgItalic.IsChecked.Value) {
                style = "italic";
            }
            else {
                style = "regular";
            }

            this._Config.SetString("Appearance", "Font Style", style);

            this.UpdatePreview();            
        }

        private void Button_Font(object sender, RoutedEventArgs e) {
            int size;
            if (!int.TryParse(this.cfgFontSize.Text, out size)) {
                size = 14;
            }
            System.Drawing.FontStyle style = System.Drawing.FontStyle.Regular;
            if (this.cfgBold.IsChecked.Value) {
                style |= System.Drawing.FontStyle.Bold;
            }
            if (this.cfgItalic.IsChecked.Value) {
                style |= System.Drawing.FontStyle.Italic;
            }
            this._FontDialog.Font = new System.Drawing.Font(
                this.cfgFontFamily.Text,
                size,
                style
            );
            this._FontDialog.ShowDialog();

            this.cfgFontFamily.Text = this._FontDialog.Font.FontFamily.Name;
            this.cfgFontSize.Text = ((int)Math.Round(this._FontDialog.Font.Size, MidpointRounding.AwayFromZero)).ToString();
            this.cfgBold.IsChecked = this._FontDialog.Font.Bold;
            this.cfgItalic.IsChecked = this._FontDialog.Font.Italic;
        }

        private void UpdatePreview() {
            Styles.OuterBorder(this.PreviewOuterBorder, this._Config);
            Styles.InnerBorder(this.PreviewInnerBorder, this._Config);
            Styles.TextBox(this.PreviewCommandTextBox, this._Config);
        }
        private int AppearanceAlignmentIndex() {
            string a = this._Config.GetString("Appearance", "Alignment", "centre centre").ToLower();
            switch (a) {
                case "left top":
                case "top left":
                    return 0;
                case "centre top":
                case "center top":
                case "top centre":
                case "top center":
                    return 1;
                case "right top":
                case "top right":
                    return 2;
                case "left centre":
                case "left center":
                case "centre left":
                case "center left":
                    return 3;
                case "right centre":
                case "right center":
                case "centre right":
                case "center right":
                    return 5;
                case "left bottom":
                case "bottom left":
                    return 6;
                case "centre bottom":
                case "center bottom":
                case "bottom centre":
                case "bottom center":
                    return 7;
                case "right bottom":
                case "bottom right":
                    return 8;
                default:
                    return 4;
            }
        }
        #endregion
        #region Behaviour
        private void Hotkey_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            this._Config.SetString("Behaviour", "Command Box Hot Key", this.cfgCommandBoxHotKey.Text);
        }
        private void DefaultCommand_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            this._Config.SetString("Behaviour", "Default Command", this.cfgDefaultCommand.Text);
        }
        private void Check_Click(object sender, RoutedEventArgs e) {
            if (!this._IsApply) { return; }

            CheckBox cb = sender as CheckBox;

            switch (cb.Name) {
                case "cfgEnableSpeech":
                    this._Config.SetBool("Behaviour", "Enable Speech", cb.IsChecked.Value);
                    break;
                case "cfgSmartUrlParsing":
                    this._Config.SetBool("Behaviour", "Smart Url Parsing", cb.IsChecked.Value);
                    break;
            }
        }
        private void List_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            TextBox t = sender as TextBox;
            
            string list = t.Text.Split(",", SplitOptions.Trim | SplitOptions.RemoveEmptyEntries)
                                .Join(", ");
            
            switch (t.Name) {
                case "cfgUrlEndings":
                    this._Config.SetString("Behaviour", "Url Endings", list);
                    break;
                case "cfgUrlProtocals":
                    this._Config.SetString("Behaviour", "Url Protocals", list);
                    break;                
            }
        }
        private void Display_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if (!this._IsApply) { return; }

            int display = this.cfgDisplay.SelectedIndex - 1;
            this._Config.SetInt("Behaviour", "Display", display);
        }

        private int DisplayIndex() {
            int monitor = this._Config.GetInt("Behaviour", "Display", -1);

            if (monitor >= 0 && monitor < System.Windows.Forms.Screen.AllScreens.Length) {
                return monitor + 1;
            }

            return 0;
        }
        #endregion        

        #region Config Events
        private void Navigation_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {
            TreeViewItem tvi = e.NewValue as TreeViewItem;
            this.SelectNavigationItem(tvi);
        }
        private void SelectNavigationItem(TreeViewItem tvi) {
            string name = tvi.Header.ToString();
            name = name.Replace(" ", string.Empty);

            if (tvi.Parent is TreeViewItem) {
                string parent = ((TreeViewItem)tvi.Parent).Header.ToString();
                if (parent == "Commands") {                    
                    this.PopulateCommandList(name);
                    name = parent;
                }
                else {
                    name = parent + name;
                }
            }
            else if (name == "Commands") {
                this.PopulateCommandList(string.Empty);
            }

            name = "pnl" + name;

            foreach (Grid g in this.pnlCollection.Children) {
                if (g.Name == name) {
                    g.Visibility = Visibility.Visible;
                }
                else {
                    g.Visibility = Visibility.Hidden;
                }
            }
        }
        private void Config_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            this.Save();

            // Unload config panels
            var plugins = from p in this._Exec.PluginService.Plugins.Keys
                          where this._Exec.PluginService.Plugins[p].Instance is IConfigurable<IniFile>
                          select p;

            foreach (string p in plugins) {
                string name = "pnlPlugins" + p;

                foreach (Grid g in this.pnlCollection.Children) {
                    if (g.Name == name) {
                        g.Children.RemoveAt(0);
                    }
                }
            }

            // Unload active command panel
            this.cmdCommandControl.Content = null;
        }
        
        private void Button_ResetAll(object sender, RoutedEventArgs e) {
            this._Config = Executioner.Config.Clone() as IniFile;
            this._HotKeys = this.HotKey_Clone();
            this._Commands = this.Commands_Clone();

            // This will cause all the events to trigger from setting each thing :/
            this.InitializeConfig();

            this.SelectNavigationItem(this.trvNavigation.SelectedItem as TreeViewItem);
        }
        private void Button_ResetPage(object sender, RoutedEventArgs e) {

        }
        private void Button_Close(object sender, RoutedEventArgs e) {            
            this.Close();
        }
        #endregion 

        #region Saving
        private void Save() {
            this._Config.Save();

            XmlWriterSettings xws = new XmlWriterSettings();
            xws.Encoding = System.Text.Encoding.UTF8;
            xws.Indent = true;
            xws.IndentChars = "    ";
            xws.NewLineChars = Environment.NewLine;
            xws.NewLineHandling = NewLineHandling.Replace;
            xws.OmitXmlDeclaration = true;

            XmlWriter xw = XmlWriter.Create("commands.xml", xws);
            
            XElement xe = new XElement("Executioner");
            xe.Add(new XAttribute("Version", "1.0"));            
            xe.Add(this.SaveHotKeys());
            xe.Add(this.SaveCommands());            
            xe.WriteTo(xw);

            xw.Close();
        }
        private XElement SaveHotKeys() {
            XElement xe = new XElement("HotKeys");

            foreach (HotKey hk in this._HotKeys) {
                if (!hk.IsEmpty) {
                    xe.Add(hk.Save());
                }
            }

            return xe;
        }
        private XElement SaveCommands() {
            XElement xe = new XElement("Commands");

            foreach (List<ICommand> cs in this._Commands.Values) {
                foreach (ICommand c in cs) {
                    if (!c.IsEmpty) {
                        xe.Add(c.Save());
                    }
                }
            }

            return xe;
        }
        #endregion

        #region Hot Keys
        private void HotKey_HotKey_TextChanged(object sender, TextChangedEventArgs e) {
            if (!this._IsApply) { return; }

            int index = this.hkAssignedList.SelectedIndex;
            if (index < 0) { return; }

            this._HotKeys[index].Modifiers = this.hkHotKey.Modifiers;
            this._HotKeys[index].Key = this.hkHotKey.Key;
            this._HotKeys[index].KeyValue = this.hkHotKey.KeyValue;
            ListBoxItem lbi = this.hkAssignedList.SelectedItem as ListBoxItem;
            if (this._HotKeys[index].IsEmpty) {
                lbi.Content = "<new hot key>";
            }
            else {
                lbi.Content = this._HotKeys[index].ToString().Trim();
            }            
        }
        private void HotKey_Command_CommandChanged(object sender, EventArgs e) {
            if (!this._IsApply) { return; }

            int index = this.hkAssignedList.SelectedIndex;
            if (index < 0) { return; }

            CommandSelector cs = sender as CommandSelector;

            CommandInfo ci = new CommandInfo(
                cs.Command,
                cs.Arguments
            );            

            if (this._HotKeys[index].Commands.Count == 0) {
                this._HotKeys[index].Commands.Add(ci);
            }
            else {
                int tabIndex = this.hkTabCommands.SelectedIndex;
                this._HotKeys[index].Commands[tabIndex] = ci;
            }
        }

        private void HotKey_AssignedList_SelectionChanged(object sender, SelectionChangedEventArgs e) {            
            this.HotKey_UpdatHotKeyeAndCommand();
        }
        
        private void HotKey_Button_AddHotKey(object sender, RoutedEventArgs e) {
            this.hkRemoveHotKey.IsEnabled = true;

            // Remove all empty hotkeys
            for (int i = this._HotKeys.Count - 1; i >= 0; i--) {
                if (this._HotKeys[i].IsEmpty) {
                    this._HotKeys.RemoveAt(i);
                }
            }

            this._HotKeys.Add(new HotKey());
            this.HotKey_UpdateAssignedList();
            this.hkAssignedList.SelectedIndex = this.hkAssignedList.Items.Count - 1;
        }
        private void HotKey_Button_RemoveHotKey(object sender, RoutedEventArgs e) {
            int index = this.hkAssignedList.SelectedIndex;
            if (index < 0) { return; }

            this._HotKeys.RemoveAt(index);
            this.hkAssignedList.Items.RemoveAt(index);

            if (this._HotKeys.Count == 0) {
                hkHotKeyCommandGroup.IsEnabled = false;
                this.hkRemoveHotKey.IsEnabled = false;
            }
            else {
                if (this.hkAssignedList.Items.Count <= index) {
                    this.hkAssignedList.SelectedIndex = index - 1;
                }
                else {
                    this.hkAssignedList.SelectedIndex = index;
                }
            }
        }

        private void HotKey_Button_AddCommand(object sender, RoutedEventArgs e) {
            int index = this.hkAssignedList.SelectedIndex;
            if (index < 0 || this.hkTabCommands.Items.Count == 8) { return; }
            
            // Since there is always at least one tab, ensure at least one command
            // info exists before adding another.
            if (this._HotKeys[index].Commands.Count == 0) {
                this._HotKeys[index].Commands.Add(new CommandInfo());
            }
            this._HotKeys[index].Commands.Add(new CommandInfo());

            TabItem ti = this.HotKey_CreateCommandTabItem(this.hkTabCommands.Items.Count);
            this.hkTabCommands.Items.Add(ti);

            if (this.hkTabCommands.Items.Count == 8) {
                this.hkAddCommand.IsEnabled = false;                
            }

            this.hkTabCommands.SelectedIndex = this.hkTabCommands.Items.Count - 1;            
        }
        private void HotKey_Button_RemoveCommand(object sender, RoutedEventArgs e) {
            int index = this.hkAssignedList.SelectedIndex;
            if (index < 0) { return; }

            this.hkAddCommand.IsEnabled = true;

            if (hkTabCommands.Items.Count == 1) {
                this._HotKeys[index].Commands.Clear();
                this.HotKey_UpdatHotKeyeAndCommand();
            }
            else if (hkTabCommands.SelectedIndex == 0) {
                this._HotKeys[index].Commands.RemoveAt(0);
                this.HotKey_UpdatHotKeyeAndCommand();
            }
            else {
                int tabIndex = hkTabCommands.SelectedIndex;
                this._HotKeys[index].Commands.RemoveAt(tabIndex);
                this.hkTabCommands.Items.RemoveAt(tabIndex);

                if (this.hkTabCommands.Items.Count <= tabIndex) {
                    this.hkTabCommands.SelectedIndex = tabIndex - 1;
                }
                else {
                    this.hkTabCommands.SelectedIndex = tabIndex;
                    // Reorder tab headers
                    int count = 0;
                    foreach (TabItem ti in this.hkTabCommands.Items) {
                        ++count;
                        ti.Header = count.ToString();
                    }
                }
            }
        }

        private void HotKey_UpdateAssignedList() {
            this.hkAssignedList.Items.Clear();
            foreach (HotKey hk in this._HotKeys) {
                ListBoxItem lbi = new ListBoxItem();
                string content = hk.ToString();
                if (content == string.Empty) {
                    lbi.Content = "<new hot key>";
                }
                else {
                    lbi.Content = content;
                }                

                this.hkAssignedList.Items.Add(lbi);
            }

            if (this.hkAssignedList.Items.Count == 0) {
                hkHotKeyCommandGroup.IsEnabled = false;
            }
            else {
                hkHotKeyCommandGroup.IsEnabled = true;
            }
        }
        private void HotKey_UpdatHotKeyeAndCommand() {
            this._IsApply = false;

            this.hkTabCommands.SelectedIndex = 0;

            int index = this.hkAssignedList.SelectedIndex;

            if (index < 0) {
                this.hkCommand.Command = string.Empty;
                this.hkCommand.Arguments = string.Empty;
                this.hkHotKey.Text = string.Empty;
            }
            else {
                HotKey hk = this._HotKeys[index];
                this.hkHotKey.Text = hk.ToString();
                if (hk.Commands.Count > 0) {
                    this.hkCommand.Command = hk.Commands[0].Command;
                    this.hkCommand.Arguments = hk.Commands[0].Arguments;
                }
                else {
                    this.hkCommand.Command = string.Empty;
                    this.hkCommand.Arguments = string.Empty;
                }
            }

            this.HotKey_UpdateTabCommands(index);

            this._IsApply = true;
        }
        private void HotKey_UpdateTabCommands(int index) {
            for (int i = this.hkTabCommands.Items.Count - 1; i >= 1; i--) {
                this.hkTabCommands.Items.RemoveAt(i);
            }

            if (index >= 0) {
                for (int i = 1; i < this._HotKeys[index].Commands.Count; i++) {
                    TabItem ti = this.HotKey_CreateCommandTabItem(i, this._HotKeys[index].Commands[i]);
                    this.hkTabCommands.Items.Add(ti);
                }
            }
        }
        private TabItem HotKey_CreateCommandTabItem(int index) {
            return this.HotKey_CreateCommandTabItem(index, new CommandInfo());
        }
        private TabItem HotKey_CreateCommandTabItem(int index, CommandInfo commandInfo) {
            TabItem ti = new TabItem();
            CommandSelector cs = new CommandSelector();

            ti.Header = (index + 1).ToString();
            cs.Command = commandInfo.Command;
            cs.Arguments = commandInfo.Arguments;

            cs.CommandChanged += new EventHandler(HotKey_Command_CommandChanged);

            ti.Content = cs;

            return ti;
        }
        private List<HotKey> HotKey_Clone() {
            List<HotKey> hks = new List<HotKey>();
            foreach (HotKey hk in this._Exec.HotKeys) {
                hks.Add(hk.Clone() as HotKey);
            }
            return hks;
        }
        #endregion

        #region Commands
        private void Command_AssignedList_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if (!this._IsApply) { return; }

            int index = this.cmdAssignedList.SelectedIndex;
            this._SelectedCommandIndexes[this._CommandType] = index;

            if (index < 0) {
                this.cmdEmptyCommand.Visibility = Visibility.Visible;
                this.cmdCommandControl.Content = null;
            }
            else {
                this.cmdEmptyCommand.Visibility = Visibility.Collapsed;

                if (this._SelectedCommand != null) {
                    this._SelectedCommand.Configurator.CommandNameChanged -= this._CommandChanged;
                }
                this._SelectedCommand = this.SelectedCommand();
                this._SelectedCommand.Configurator.CommandNameChanged += this._CommandChanged;
                this.cmdCommandControl.Content = this._SelectedCommand.Configurator.ConfigPanel;
            }
        }
        private void Configurator_CommandNameChanged(object sender, CommandNameEventArgs e) {
            string name = e.Name;

            if (name == string.Empty) {
                string type;
                if (this._CommandType == string.Empty) {                    
                    ICommand c = this.SelectedCommand();
                    if (c != null) {
                        type = this.CommandName(c).ToLower();
                    }
                    else {
                        type = "command";
                    }
                }
                else {
                    type = this._CommandType.SeparateWords().ToLower();
                }

                name = "<new " + type + ">";
            }
            else if (this._CommandType == string.Empty) {
                ICommand c = this.SelectedCommand();
                if (c != null) {
                    name = "[" + this.CommandName(c) + "] " + name;
                }
            }

            ((ListBoxItem)this.cmdAssignedList.SelectedItem).Content = name;   
        }
        private ICommand SelectedCommand() {
            int index = this.cmdAssignedList.SelectedIndex;

            if (this._CommandType != string.Empty) {                
                return this._Commands[this._CommandType][index];
            }
            else {
                int count = 0;
                foreach (List<ICommand> cs in this._Commands.Values) {
                    foreach (ICommand c in cs) {
                        if (count == index) {
                            return c;
                        }
                        ++count;
                    }
                }
            }

            return null;
        }

        private void Command_AddCommand_Click(object sender, RoutedEventArgs e) {
            this.cmdRemoveCommand.IsEnabled = true;

            this._IsApply = false;

            // Remove all empty HotStrings
            for (int i = this._Commands[this._CommandType].Count - 1; i >= 0; i--) {
                if (this._Commands[this._CommandType][i].IsEmpty) {
                    this._Commands[this._CommandType].RemoveAt(i);
                }
            }

            this._IsApply = true;

            ICommand command;
            if (this._CommandType == "HotString") {
                command = new HotString();
            }
            else {
                PluginInstance<Plugin> plugin = this._Exec.PluginService[this._CommandType];
                if (plugin == null || !(plugin.Instance is CommandPlugin)) { return; }
                command = ((CommandPlugin)plugin.Instance).InitializeCommand();
            }

            if (command != null) {
                this._Commands[this._CommandType].Add(command);
                this.cmdAssignedList.Items.Add(this.CommandListItem(command, false));
                this.cmdAssignedList.SelectedIndex = this.cmdAssignedList.Items.Count - 1;
                this.cmdAssignedList.ScrollIntoView(this.cmdAssignedList.SelectedItem);
            }
            else {
                System.Media.SystemSounds.Beep.Play();
            }
        }
        private void Command_RemoveCommand_Click(object sender, RoutedEventArgs e) {
            int index = this.cmdAssignedList.SelectedIndex;
            if (index < 0) { return; }            

            this._Commands[this._CommandType].RemoveAt(index);
            this.cmdAssignedList.Items.RemoveAt(index);

            if (this.cmdAssignedList.Items.Count == 0) {
                this.cmdRemoveCommand.IsEnabled = false;
            }
            else if (this.cmdAssignedList.Items.Count <= index) {
                this.cmdAssignedList.SelectedIndex = index - 1;
            }
            else {
                this.cmdAssignedList.SelectedIndex = index;
            }
        }

        private void PopulateCommandList(string type) {
            // Prevent selection change event from being triggered and updating 
            // selected command value before it's used
            this._IsApply = false;

            this._CommandType = type;
            this.cmdAssignedList.Items.Clear();

            if (type == string.Empty) {
                this.cmdButtons.Visibility = Visibility.Collapsed;

                foreach (List<ICommand> cs in this._Commands.Values) {
                    foreach (ICommand c in cs) {
                        this.cmdAssignedList.Items.Add(this.CommandListItem(c, true));
                    }
                }
            }
            else {
                this.cmdButtons.Visibility = Visibility.Visible;

                foreach (ICommand c in this._Commands[type]) {
                    this.cmdAssignedList.Items.Add(this.CommandListItem(c, false));
                }                
            }

            this._IsApply = true;

            if (this.cmdAssignedList.Items.Count == 0) {
                this.cmdRemoveCommand.IsEnabled = false;
            }
            else {
                this.cmdRemoveCommand.IsEnabled = true;
            }

            if (this._SelectedCommandIndexes[type] < 0) {
                this.cmdEmptyCommand.Visibility = Visibility.Visible;
                this.cmdCommandControl.Content = null;
            }
            else if (this._SelectedCommandIndexes[type] >= this.cmdAssignedList.Items.Count) {
                this.cmdEmptyCommand.Visibility = Visibility.Visible;
                this.cmdCommandControl.Content = null;
                this._SelectedCommandIndexes[type] = -1;
            }
            else {
                this.cmdAssignedList.SelectedIndex = this._SelectedCommandIndexes[type];
            }
        }
        private ListBoxItem CommandListItem(ICommand command, bool prefix) {
            ListBoxItem lbi = new ListBoxItem();            
            string type = this.CommandName(command);
            
            if (command.IsEmpty) {
                lbi.Content = "<new " + type.ToLower() + ">";
            }
            else {
                if (prefix) {
                    lbi.Content = "[" + type + "] " + command.ToString();
                }
                else {
                    lbi.Content = command.ToString();
                }
            }

            return lbi;
        }

        private string CommandName(ICommand command) {
            string name = command.GetType().Name;
            if (name.EndsWith("Command")) {
                name = name.Substring(0, name.Length - 7);
            }
            return name.SeparateWords();
        }

        private Dictionary<string, List<ICommand>> Commands_Clone() {
            Dictionary<string, List<ICommand>> cmds = new Dictionary<string, List<ICommand>>();

            foreach (string type in this._Exec.Commands.Keys) {
                cmds[type] = this.Commands_Clone(type);
            }

            return cmds; 
        }
        private List<ICommand> Commands_Clone(string type) {
            List<ICommand> cmds = new List<ICommand>();
            foreach (ICommand c in this._Exec.Commands[type]) {
                cmds.Add(c.Clone() as ICommand);
            }
            return cmds;
        }
        #endregion
    }
}
